home *** CD-ROM | disk | FTP | other *** search
- {
- > I have a Turbo Pascal "interrupt" routine which "catches" incoming
- > characters from a COM port and stashes them in a circular buffer.
- > While it seems to work OK most of the time, occasionally it misses
- > a character (it can NOT keep up with 600 baud, but Kermit does quite
- > well at 9600 baud, so I know it can be "fixed"). Here is the code:
- > (Please ignore the BEGINPROCEDURE, ENDIF, etc.; I use a pre-processor
- > to translate Pascal-as-it-ought-to-be-IMHO into Turbo Pascal.)
- >
- I don't know what the trouble is with your interrupt routine, but I wrote one
- about 6 months ago for a friend to use and it works fine on my machine (386
- 33) at 2400 and on my friends machine (486 66) at 9600. Here it is, hope it
- helps.
-
- This unit is an array implementation of a queue, used to store incoming
- characters. An array is used instead of a linked list because I believed it
- would be faster, and less overhead.
- }
- UNIT QPak;
- {$R-}
- {Range checking must be turned off, so as to permit the little trick with
- the array}
-
- INTERFACE
-
- TYPE
- ElementType = Char;
-
- ElementArray = ARRAY[0..0] OF Char;
-
- QUEUE = RECORD
- Front,
- Rear : Word;
- EL : ^ElementArray;
- Size : Word;
- Count : Word;
- END;
-
- PROCEDURE MakeQueueEmpty(VAR Q : Queue;
- QSize : Word);
-
- FUNCTION QueueIsEmpty(Q : Queue) : Boolean;
-
- FUNCTION QueueIsFull(Q : Queue) : Boolean;
-
- PROCEDURE Enqueue(VAR Q : Queue;
- Element : ElementType);
-
- PROCEDURE Dequeue(VAR Q : Queue;
- VAR Element : ElementType);
-
- IMPLEMENTATION
-
-
- PROCEDURE MakeQueueEmpty(VAR Q : Queue; QSize : Word);
-
- BEGIN
- GetMem(Q.EL,QSize);
- Q.Front := 1;
- Q.Rear := 0;
- Q.Size := QSize;
- Q.Count := 0;
- END;
-
- FUNCTION QueueIsEmpty(Q : Queue) : Boolean;
-
- BEGIN
- QueueIsEmpty := (Q.Count = 0);
- END;
-
- FUNCTION QueueIsFull(Q : Queue) : Boolean;
-
- BEGIN
- QueueIsFull := (Q.Count = Q.Size);
- END;
-
-
- PROCEDURE Enqueue(VAR Q : Queue; Element : ElementType);
-
- BEGIN
- WITH Q Do BEGIN
- Rear := (Rear + 1) MOD Size;
- EL^[Rear] := Element;
- Inc(Count);
- END;
- END;
-
- PROCEDURE Dequeue(VAR Q : Queue; VAR Element : ElementType);
-
- BEGIN
- WITH Q DO BEGIN
- Element := EL^[Front];
- Front := (Front + 1) MOD Size;
- Dec(Count);
- END;
- END;
-
- END.
- {
- -----------------------CUT HERE--------------------
-
- Here is the com unit. I've commented about everyline (since it was for a
- friend) so hopefilly my comments are understandable.
-
- -----------------------CUT HERE---------------------
- }
- UNIT ComUnit;
-
- INTERFACE
-
- USES DOS, CRT, QPak;
-
- PROCEDURE InitPort(ComPort,
- Parity,
- Stop,
- WLength : Byte;
- Speed : Word);
-
- FUNCTION CharReady(ComPort : Byte) : Boolean;
-
- {This procedure writes a char to desired port}
- PROCEDURE SendChar(Ch : Char; ComPort : Byte);
-
- {This function reads a char from the serial port by dequeueing and element}
- FUNCTION GetChar(ComPort : Byte) : Char;
-
- PROCEDURE ShutDown(ComPort : Byte);
-
- TYPE
- UART = RECORD
- THR : Integer; {Transmit Holding Register}
- RBR : Integer; {Receive Holding Register}
- IER : Integer; {Interrupt enable Regeister}
- LCR : Word; {Line Control Register}
- MCR : Integer; {Modem Control Register}
- LSR : Integer; {Line Status Register}
- MSR : Integer; {Modem Status Register}
- IRQ : Integer;
- DLL : Word;
- DLM : WOrd;
- END;
-
- {This array holds the buffers for each port}
- BufferArray = ARRAY[1..4] OF Queue;
- {Here is where we save the old interrupt vectors}
- PointerArray = ARRAY[1..4] OF Pointer;
-
-
- CONST
- {The following are constants used in initialization, and for port adressing}
- COM1 = 1;
- COM2 = 2;
- COM3 = 3;
- COM4 = 4;
-
- {Baud rate divisors}
- B600 = 192;
- B1200 = 96;
- B2400 = 48;
- B4800 = 24;
- B9600 = 12;
- B19200 = 6;
- B38400 = 3; {If your really feeling frisky}
-
- {Parity masks}
- NoParity = 0;
- OddParity = $8;
- EvenParity = $18;
-
- {Stop bit masks}
- OneStopBit = 0;
- TwoStopBit = 2;
-
- {OR-Mask to set divisor latch in line control register}
- DLatch = $80;
-
- {Port address for interrupt mask port of 8259A}
- IntMaskPort = $21;
-
- {Port address for 8259 interrupt control, used to send EOI}
- IntCtlPort = $20;
-
- {Masks for different word lengths}
- Word5 = 0;
- Word6 = 1;
- Word7 = 2;
- Word8 = 3;
-
- IMPLEMENTATION
-
- CONST
- {Typed constant that contains all registers addresses for Com1..Com4}
- RS232 : ARRAY[1..4] OF UART =
-
- ((THR:$3F8;RBR:$3F8;IER:$3F9;LCR:$3FB;MCR:$3FC;LSR:$3FD;MSR:$3FE;IRQ:4;DLL:$3F8
- ;
- LM:$3F9),
-
- (THR:$2F8;RBR:$2F8;IER:$2F9;LCR:$2FB;MCR:$2FC;LSR:$2FD;MSR:$2FE;IRQ:3;DLL:$2F8;
- LM:$2F9),
-
- (THR:$3E8;RBR:$3E8;IER:$3E9;LCR:$3EB;MCR:$3EC;LSR:$3ED;MSR:$3EE;IRQ:4;DLL:$3E8;
- LM:$3E9),
-
- (THR:$2E8;RBR:$2E8;IER:$2E9;LCR:$2EB;MCR:$2EC;LSR:$2ED;MSR:$2EE;IRQ:3;DLL:$2E8;
- LM:$2E9));
-
-
- VAR
- Buffers : BufferArray;
- IntVecsSave : PointerArray;
-
- {Inline Macros}
- PROCEDURE DisableInterrupts ; inline( $FA {cli} ) ;
- PROCEDURE EnableInterrupts ; inline( $FB {sti} ) ;
-
- {Here is the interrupt procedure for com3, its address is put int the int
- Vec table by InitPort}
- PROCEDURE Com13ISR; INTERRUPT;
-
- BEGIN
- {Read the character from the port and put it in the queue}
- Enqueue(Buffers[Com3],Char(Port[RS232[Com3].RBR]));
- Port[IntCtlPort] := $20; {Non-specific EOI}
- END;
-
- PROCEDURE Com24ISR; INTERRUPT;
-
- BEGIN
- {Read the character from the port and put it in the queue}
- Enqueue(Buffers[Com3],Char(Port[RS232[Com3].RBR]));
- Port[IntCtlPort] := $20; {Non-specific EOI}
- END;
-
- {---------------------------------------------------------------}
- { +++InitPort+++ }
- { }
- { ComPort: A byte specifying the comport to use Range 1..4 }
- { Speed : This is really the baud rate divisor The predefined }
- { constants are the correct divisors for those speeds }
- { Parity, }
- { Stop, }
- { WLength: These are all bit-masks used to build }
- { the line format byte }
- {---------------------------------------------------------------}
- PROCEDURE InitPort(ComPort,
- Parity,
- Stop,
- WLength : Byte;
- Speed : Word);
-
- VAR
- LineFormat : Byte;
-
-
- BEGIN
- MakeQueueEmpty(Buffers[ComPort],2048);
- LineFormat := 0;
- {Build the line format byte}
- LineFormat := LineFormat OR WLength OR Stop OR Parity;
- {Set divisor latch so we can set baud rate}
- Port[RS232[ComPort].LCR] := LineFormat AND DLatch;
- {Now we set baud rate, least sig part of divisor sent first then most sig}
- Port[RS232[ComPort].DLL] := Low(Speed);
- Port[RS232[ComPort].DLM] := Hi(Speed);
- {Now set line format}
- Port[RS232[ComPort].LCR] := LineFormat;
- {Must set out2 of modem control reg for interrupts, so we do it here}
- Port[RS232[ComPort].MCR] := $0B;
- {Save interrupt vector so we can restore it later, then set vector to
- point at our ISR}
-
- {Now we must unmask appropriate int line in 8259A interrupt controller
- We are using IRQ4 for com1 and 3, and IRQ3 for com2 and 4, use of any
- other IRQ line will require changes to the code}
- IF ODD(ComPort) THEN BEGIN
- GetIntVec(RS232[ComPort].IRQ+8,IntVecsSave[ComPort]);
- SetIntVec(RS232[ComPort].IRQ+8,@Com13ISR);
- Port[IntMaskPort] := Port[IntMaskPort] AND $EF
- END ELSE BEGIN
- GetIntVec(RS232[ComPort].IRQ+8,IntVecsSave[ComPort]);
- SetIntVec(RS232[ComPort].IRQ+8,@Com24ISR);
- Port[IntMaskPort] := Port[IntMaskPort] AND $F7;
- END;
- {Here we tell 8250 UART to interrupt on received chars}
- DisableInterrupts;
- Port[Rs232[ComPort].IER] := 1;
- EnableInterrupts;
-
- END;
-
- {This function returns true if there are any chars in the buffer}
- FUNCTION CharReady(ComPort : Byte) : Boolean;
-
- BEGIN
- CharReady := NOT QueueIsEmpty(Buffers[ComPort]);
- END;
-
- {This procedure writes a char to desired port}
- PROCEDURE SendChar(Ch : Char; ComPort : Byte);
-
- BEGIN
- {Loop until transmit holding register empty}
- WHILE (Port[RS232[ComPort].LSR] AND $20) <> $20 DO
- Delay(1);
- Port[RS232[ComPort].THR] := Byte(Ch);
- END;
-
- {This function reads a char from the serial port by dequeueing and element}
- FUNCTION GetChar(ComPort : Byte) : Char;
-
- VAR
- Ch : Char;
-
- BEGIN
- Dequeue(Buffers[ComPort],Ch);
- GetChar := Ch;
- END;
-
-
- PROCEDURE ShutDown(ComPort : Byte);
-
- BEGIN
- SetIntVec(RS232[ComPort].IRQ+8,IntVecsSave[ComPort]);
- END;
-
- END.
-
- {
- ------------------CUT HERE---------------------
-
- One remark is probably appropriate here. My friend had the need to read two
- ports simultaneously so that is why there are two interrupt rountine, one com
- 1 and 3 and one for com 2 and 4, since they use the same IRQ lines.
-
- Here is a little test program I used.
-
- -----------------CUT HERE----------------------
- }
- USES CRT, ComUnit;
- VAR
- Ch : Char;
- Done : Boolean;
-
- BEGIN
- Done := FALSE;
- InitPort(Com3,NoParity,OneStopBit,Word8,B2400);
- ClrScr;
- Writeln('Com test in progress. F1 to exit');
- REPEAT
- IF CharReady(Com3) THEN BEGIN
- Ch := GetChar(Com3);
- Write(Ch);
- END
- ELSE IF Keypressed THEN BEGIN
- Ch := ReadKey;
- IF CH = #0 THEN BEGIN {Extended key scan code}
- Ch := Readkey;
- IF Ch = #59 THEN {F1}
- Done := True;
- END ELSE
- SendChar(Ch,Com3);
- END
- UNTIL Done;
- ShutDown(Com3);
- END.
- {
-
- I hope this helps. It does work, although there could some thing wrong given
- I'm no expert. I also wrote some routines in assember about a year and a
- half ago, so if you really want assembly code I'd be happy to did them out.
- }
-